/* eslint-disable no-console */
/* eslint-disable no-use-before-define */
/* eslint-disable no-unused-vars */
/* eslint-disable no-restricted-syntax */
const fs = require('fs-extra');
const _ = require('underscore');
const util = require('util');
const iconv = require('iconv-lite');
const logger = require('../../logger');
const utility = require('../../utility');
const files = require('../../files');
const functions = require('../../functions');
const FileTypeSystem = require('../../mongo/file-type-system');
const { addTest, stubKind } = require('../../tests');

const { getValueByKeyPath, setValueByKeyPath, setValueByKeyPathArray } = require('../../utility');

function isCArrayType(typeString) {
  // 检查类型字符串中是否包含 `[]` 或 `[size]` 的数组标识
  return /\[\d*\]$/.test(typeString);
}
function parseArrayTypeStr(str) {
  const regex = /^([\w\s]+?)\s*\[\s*(\d+)\s*\]$/;
  const match = str.match(regex);
  if (match) {
    return {
      elementType: match[1].trim(),
      length: parseInt(match[2], 10),
    };
  }
  return null;
}

function isValidCVariableName(name) {
  // 定义 C 语言变量名的正则表达式
  const regex = /^[a-zA-Z_][a-zA-Z0-9_]*$/;

  // 定义 C 语言关键字列表
  const keywords = [
    'auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do', 'double', 'else', 'enum', 'extern',
    'float', 'for', 'goto', 'if', 'int', 'long', 'register', 'return', 'short', 'signed', 'sizeof', 'static',
    'struct', 'switch', 'typedef', 'union', 'unsigned', 'void', 'volatile', 'while',
  ];

  // 检查是否匹配正则表达式且不是关键字
  return regex.test(name) && !keywords.includes(name);
}

// 判断是否只包含点操作符和数组下标操作符，例如a.b[0].c
function onlyDotAndArrayIndex(str) {
  const reg = /^[a-zA-Z_]\w*(\[\d+\]|\.\w+)*$/;
  return reg.test(str);
}

/**
 * 拆分路径，适配结构体、数组
 * @param {string} keyPath a.b[0].c
 * @returns {[string]} 拆分后的路径 ['a', '@value', 'b', '@value', '0', '@value', 'c']
 */
function splitPath(keyPath) {
  if (!onlyDotAndArrayIndex(keyPath)) {
    throw new Error('只能包含点操作符或数组下标操作符');
  }
  // eslint-disable-next-line no-useless-escape
  const regex = /([^\[\].]+)/g;
  return keyPath.match(regex).join('.@value.').split('.');
}

/**
 *
 * @param {[string]} keypaths ['a', '@value', 'b', '@value', '0', '@value', 'c']
 * @param {string} rootTypeName
 * @param {Object} typeSystem
 */
function getKeyPathTypes(keypaths, rootTypeName, typeSystem) {
  keypaths = keypaths.filter((item) => item !== '@value');

  let rootType = typeSystem[rootTypeName];
  const types = [rootTypeName];
  console.log(keypaths);
  console.log(rootTypeName, rootType);
  if (!rootType) {
    // 如果类型系统中不存在，尝试自己解析数组类型
    // eslint-disable-next-line no-use-before-define
    const ret = parseArrayTypeStr(rootTypeName);
    if (ret) {
      rootType = {
        '@attributes': ['isArray'],
        '@elementType': ret.elementType,
        '@length': ret.length,
      };
    } else {
      throw new Error(`not found type ${rootTypeName} in typesystem`);
    }
  }
  if (rootType['@attributes'].find((item) => item === 'isPointer')) {
    rootType = typeSystem[rootType['@pointeeType']];
  }
  for (let i = 0; i < keypaths.length; i += 1) {
    const next = keypaths[i + 1];
    if (!next) {
      break;
    }
    if (rootType['@attributes'].find((item) => item === 'isStructure')) {
      // 结构体
      const nextField = rootType['@fields'][next];
      if (!nextField) {
        throw new Error(`not found filed ${next} under type ${rootType}`);
      }
      const nextTypeName = nextField['@type'];
      const nextType = typeSystem[nextTypeName];
      if (!nextType) {
        throw new Error(`not found type ${nextTypeName}`);
      }
      types.push(nextTypeName);
      rootType = nextType;
    } else if (rootType['@attributes'].find((item) => item === 'isArray')) {
      // 数组
      if (Number.isInteger(parseInt(next, 10))) {
        // 数组下标
        const elementTypeName = rootType['@elementType'];
        const elementType = typeSystem[elementTypeName];
        if (!elementType) {
          throw new Error(`not found type ${elementTypeName}`);
        }
        types.push(elementTypeName);
        rootType = elementType;
      }
    } else {
      throw new Error(`非数组和结构体不应有后置节点${JSON.stringify(rootType, null, 1)}`);
    }
  }
  return types;
}

function json2TestData(fileJson, typeSystem) {
  const fileName = fileJson.name;
  const filePath = fileJson.path;
  const functionTestCases = [];
  Object.keys(fileJson.data).forEach((functionName) => {
    const func = fileJson.data[functionName];
    const mallocVariable = {};
    const testcases = [];
    // const defaultPtrTargets = getValueByKeyPath(func, 'data.entry.defaultPtrTargets') || [];
    const usingGvNames = getValueByKeyPath(func, 'data.entry.usingGvNames') || {};
    const userDefVars = getValueByKeyPath(func, 'data.entry.userDefVars'); // malloc
    const userFunc = getValueByKeyPath(func, 'data.userFunc'); // 用户创建的桩函数
    if (userDefVars || userDefVars.length) {
      for (const item of userDefVars) {
        const { varName, type } = item;
        mallocVariable[varName] = {
          '@type': type,
        };
      }
    }
    const cases = getValueByKeyPath(func, 'data.cases') || [];
    for (const tc of cases) {
      const output = {};
      const global = {};
      const globalOutput = {};
      const params = {};
      const stubs = {};
      const stubsOutput = {};
      const malloc = {};
      const mallocOutput = {};
      const preludes = [];
      const {
        gVars, paraExprs, stubInss, userDefExprs, result,
      } = tc;
      // 返回值
      if (result.status === 'OK') {
        const retVal = result.obValues.find((item) => item.expr === 'returnValue');
        if (retVal) {
          setValueByKeyPath(output, '%.@value', retVal.value);
        }
      }
      // 全局变量
      for (const g of gVars) {
        const { expr, in: input, out: output } = g;
        if (onlyDotAndArrayIndex(expr.replace(/[()]/g, ''))) {
          const keyPath = splitPath(expr.replace(/[()]/g, ''));
          setValueByKeyPathArray(global, [...keyPath, '@value'], input);
          if (output) {
            setValueByKeyPathArray(globalOutput, [...keyPath, '@value'], output);
          }
        } else if (input) {
          // 暂时忽略所有的带指针箭头访问的表达式，因为可能会造成运行时错误：空指针访问
          // preludes.push(`${expr} = ${input};`);
          // todo: 忽略output
        }
      }
      // 参数
      for (const param of paraExprs) {
        const { expr, in: input } = param;
        // todo: 不考虑*p、a->b
        if (onlyDotAndArrayIndex(expr.replace(/[()]/g, ''))) {
          const keyPath = splitPath(expr.replace(/[()]/g, ''));
          setValueByKeyPathArray(params, [...keyPath, '@value'], input);
        } else {
          // 暂时忽略所有的带指针箭头访问的表达式，因为可能会造成运行时错误：空指针访问
          // preludes.push(`${expr} = ${input};`);
        }
      }
      // 桩函数
      for (const stub of stubInss) {
        const {
          inputs, stubName, expects, result,
        } = stub;
        if (result.status === 'OK') {
          for (const { expr, value } of result.obValues) {
            if (expr === `${stubName}_call_counter`) {
              // 调用次数检查项
              setValueByKeyPath(stubsOutput, `${stubName}.@countExpected`, value);
            }
          }
        }
        const codeStub = !inputs.every((input) => onlyDotAndArrayIndex(input.expr.replace(/[()]/g, '')));
        if (codeStub) {
          // 代码打桩
          let callCountMax = 0;
          let stubCodes = 'static int call_count = 0;\ncall_count ++;\n'; // 模拟多次调用结果
          const obj = inputs.reduce((obj, input) => {
            const { expr, value } = input;
            const vals = value.split(';');
            obj[expr] = vals;
            callCountMax = callCountMax > vals.length ? callCountMax : vals.length;
            return obj;
          }, {});
          let lastReturnVal;
          for (let i = 0; i < callCountMax; i += 1) {
            const time = i + 1;
            stubCodes += `if (call_count == ${time}) {\n`;
            let ret;
            // eslint-disable-next-line no-loop-func
            Object.keys(obj).forEach((expr) => {
              const vals = obj[expr];
              const currentVal = vals[i];
              if (currentVal) {
                if (expr === 'returnValue') {
                  ret = currentVal;
                  lastReturnVal = currentVal;
                } else {
                  stubCodes += `${expr} = ${currentVal};\n`;
                }
              }
            });
            if (ret) {
              stubCodes += `return ${ret};\n`;
            }
            stubCodes += '}\n';
          }
          stubCodes += `return ${lastReturnVal};\n`;
          console.log(stubCodes);
          setValueByKeyPath(stubs, `${stubName}.body`, stubCodes);
          setValueByKeyPath(stubs, `${stubName}.kind`, 2);
          setValueByKeyPath(stubs, `${stubName}.times`, callCountMax - 1);
        } else {
          // 赋值打桩
          for (const input of inputs) {
            const keyPath = splitPath(input.expr.replace(/[()]/g, '')); // 去掉括号
            // 同时兼容returnValue, returnValue.x, returnValue[0]等情况以及他们的组合
            if (keyPath[0] === 'returnValue') { // todo: 暂时只考虑桩函数返回值，参数暂不考虑
              input.value.split(';').forEach((val, idx) => {
                // setValueByKeyPath(stubs, `${stubName}.@value.${idx}.%.@value`, val);
                setValueByKeyPathArray(stubs, [...[stubName, '@value', idx, '%'], ...keyPath.slice(1), '@value'], val);
              });
              // 调用次数
              setValueByKeyPath(stubs, `${stubName}.times`, input.value.split(';').length - 1);
            }
          }
        }

        // 桩函数参数检查项
        expects.forEach((ex, expectIdx) => {
          const { expr, value } = ex;
          // todo: 只考虑简单赋值，其他情况例如*p暂不考虑
          if (isValidCVariableName(expr)) {
            value.split(';').forEach((val, callIdx) => {
              // todo: 不确定expectIdx是否就是桩函数参数索引
              setValueByKeyPath(stubsOutput, `${stubName}.@value.${callIdx}.params.${expectIdx}.@value`, val);
            });
          }
        });
      }
      // malloc
      for (const ud of userDefExprs) {
        const { expr, in: input, out: output } = ud;
        if (onlyDotAndArrayIndex(expr.replace(/[()]/g, ''))) {
          // 简单赋值以及a.b[10]这种形式
          const keyPath = splitPath(expr.replace(/[()]/g, '')); // 去掉括号
          setValueByKeyPathArray(malloc, [...keyPath, '@value'], input);
          if (output) {
            setValueByKeyPathArray(mallocOutput, [...keyPath, '@value'], output);
          }
        } else {
          // a->b->c这种形式，直接用preload
          // 暂时忽略所有的带指针箭头访问的表达式，因为可能会造成运行时错误：空指针访问
          // preludes.push(`${expr} = ${input};`);

          const [prefix] = expr.split('->');
          if (onlyDotAndArrayIndex(prefix.replace(/[()]/g, ''))) {
            const keyPath = splitPath(prefix.replace(/[()]/g, '')); // 去掉括号
            const [mallocName] = keyPath;
            const mallocval = mallocVariable[mallocName];
            if (mallocval) {
              // const types = getKeyPathTypes(keyPath, malloc['@type'], typeSystem);
              // const typeName = types[types.length - 1];
              // const type = typeSystem[typeName];
              try {
                const ret = parsePointerChain(expr, mallocval['@type'], typeSystem, input);
                console.log(ret);
                const { kvs, mallocs } = ret;
                // malloc回填
                Object.keys(mallocs).forEach((m) => {
                  mallocVariable[m] = mallocs[m];
                });
                Object.keys(kvs).forEach((k) => {
                  setValueByKeyPath(malloc, k, kvs[k]);
                });
              } catch (error) {
                console.log(error);
              }
            }
          }
        }
      }

      testcases.push({
        data: {
          stubs,
          malloc,
          variables: {
            global,
            params,
          },
          output: {
            ...output,
            global: globalOutput,
            malloc: mallocOutput,
            stubs: stubsOutput,
          },
          prelude: preludes.join('\n'),
        },
        description: tc.desc,
      });
    }

    functionTestCases.push({
      functionName,
      fileName,
      filePath,
      testCases: testcases,
      // 需要回填到functionVariable的数据
      malloc: mallocVariable,
      userFunc,
    });
  });

  return functionTestCases;
}

/**
 * note!!!目前发现的不支持的情况
 * 1. xuanyu用例中缺少绝对地址相关信息，所以有绝对地址的情况会引发运行时错误, 非法指针访问
 * 2. 形参使用指针箭头表达式进行赋值，例如node->next = 5；如果强行放到prelude中的话可能会引发空指针错误或未定义行为(demox中就发现1个：函数test_list)
 * 3. 部分用例数据有问题，导致运行时错误或预期值不符合；部分用例会因为内存未初始化，导致内存值是随机的，从而导致结果不符合预期。
 * 4. 部分源码内有类似node->next->next->next->next->next->value的表达式，但用例中没有赋值。会报运行时错误，因为指针没有初始化，从而非法指针访问(函数test_list)
 */
async function importDataTests(tcfPath, version, zip = true) {
  const { _id: versionId } = version;
  const testsDir = `${tcfPath}.dest`;
  let dataFiles;
  await fs.remove(testsDir);
  if (zip) {
    await utility.uncompress(tcfPath, testsDir);
    dataFiles = await utility.getFiles(testsDir, ['.data']);
  } else {
    dataFiles = [tcfPath];
  }
  dataFiles = dataFiles.filter((item) => item.indexOf('__MACOSX') < 0);
  // logger.verbose(dataFiles);
  const allFileMap = await files.getSourceFileByVersionId(versionId, null, { _id: 1, path: 1, fileName: 1 }, 'path');
  const allFuncMap = await functions.getFunctionByVersionId(versionId, {
    _id: 1, fileId: 1, functionName: 1, functionAttrs: 1, kind: 1,
  });
  const functionTests = _.flatten(await Promise.all(dataFiles.map(async (tcf) => {
    const buf = await fs.readFile(tcf, 'binary');
    const tcfStr = iconv.decode(buf, 'gb18030');
    const fileJson = JSON.parse(tcfStr);
    const { name: fileName } = fileJson;
    const fileExisted = [...allFileMap.values()].find((f) => f.fileName === fileName);
    if (!fileExisted) {
      return [];
    }
    // 只考虑单元测试
    const typeSystem = await FileTypeSystem.findOne({
      fileId: fileExisted._id,
    });
    // eslint-disable-next-line no-use-before-define
    return json2TestData(fileJson, typeSystem.types);
  }))).filter((func) => {
    // fileName和functionName同时匹配
    const fileExisted = [...allFileMap.values()].find((f) => f.fileName === func.fileName);
    if (!fileExisted) {
      return false;
    }
    func.fileId = fileExisted._id;
    const funcExisted = [...allFuncMap.values()]
      .find((fun) => {
        let { functionName } = func;
        if (functionName === 'main_under_aunit_test') {
          functionName = 'main';
        }
        return fun.functionName === functionName && fun.fileId.toString() === func.fileId.toString();
      });
    if (!funcExisted) {
      return false;
    }
    func.functionId = funcExisted._id;
    return true;
  });

  await utility.arrayChunkOperation(functionTests, 10, async (funcTest) => {
    const {
      functionId, testCases, malloc, fileId, userFunc,
    } = funcTest;
    await updateFunctionVariable(functionId, testCases, { malloc, userFunc });
    await Promise.all(testCases.map((testData) => addTest(version.projectId, versionId, fileId, functionId, {
      data: testData.data,
      description: testData.description,
    })));
  });
}

/**
 * a.b->c->d[0].x->a->b[0].x = 2
 * @param {String} expr
 * @param {*} rootType
 * @param {*} typeSystem
 * @param {*} param3
 * @returns
 */
function parsePointerChain(expr, rootTypeName, typeSystem, val, {
  kvs = {}, // 赋值的键值对
  mallocs = {}, // 新创建的malloc
} = {}) {
  const nodes = expr.split('->');
  const [prefix, next, ...rest] = nodes;
  console.log('parsePointerChain:', expr, rootTypeName);
  if (!onlyDotAndArrayIndex(prefix.replace(/[()]/g, ''))) {
    throw new Error(`not onlyDotAndArrayIndex: ${prefix}`);
  }
  const keyPaths = splitPath(prefix.replace(/[()]/g, ''));
  const types = getKeyPathTypes(keyPaths, rootTypeName, typeSystem);
  let prefixTypeName = types[types.length - 1];
  let prefixType = typeSystem[prefixTypeName];
  if (!prefixType) {
    throw Error('need prefixType');
  }
  if (prefixType['@pointeeType']) {
    prefixTypeName = prefixType['@pointeeType'];
    prefixType = typeSystem[prefixType['@pointeeType']];
  }
  if (!next) {
    // 遍历结束
    const k = [...keyPaths, '@value'].join('.');
    kvs[k] = val;
    return { kvs, mallocs };
  }
  if (!onlyDotAndArrayIndex(next.replace(/[()]/g, ''))) {
    throw new Error(`not onlyDotAndArrayIndex: ${next}`);
  }
  const [field, ...restNext] = splitPath(next.replace(/[()]/g, ''));
  const fieldTypeName = getValueByKeyPath(prefixType, `@fields.${field}.@type`);
  if (!fieldTypeName) {
    throw new Error(`not found field ${field} under root type`);
  }
  const fieldType = typeSystem[fieldTypeName];
  if (!fieldType) {
    throw Error(`not found type ${fieldTypeName}`);
  }
  const mallocName = `target_${keyPaths.join('').replace(/@value/g, '_')}`;
  mallocs[mallocName] = {
    '@type': prefixTypeName,
  };
  const keyPathsFull = [...keyPaths, '@value'].join('.');
  kvs[keyPathsFull] = `&${mallocName}`;
  if (nodes.length > 1) {
    // 还未遍历结束
    const prefixNew = [mallocName, field, ...restNext].filter((item) => item !== '@value').join('.'); // 转为a.b.0.c的形式
    return parsePointerChain([prefixNew, ...rest].join('->'), prefixTypeName, typeSystem, val, { kvs, mallocs });
  }
  return { kvs, mallocs };
}

async function updateFunctionVariable(functionId, testCases, {
  malloc = {},
  userFunc = [],
}) {
  const ps = [];

  const funcVariable = (await functions.getFunctionVariables(functionId)).toJSON();

  // malloc回填
  const mallocs = Object.keys(malloc).reduce((arr, varName) => {
    const m = malloc[varName];
    const parsed = parseArrayTypeStr(m['@type']);
    if (parsed) {
      arr.push({
        typeName: m['@type'],
        elementTypeName: parsed.elementType,
        length: parsed.length,
        variableName: varName,
      });
    } else {
      arr.push({
        typeName: m['@type'],
        variableName: varName,
      });
    }
    return arr;
  }, []);

  // 用户创建的桩函数回填
  const userStubs = userFunc.map((uf) => ({
    stubName: uf.funcName,
    defaultKind: stubKind.value,
    paramTypeNames: uf.paramTypes.split(';'),
    paramNames: [],
    returnType: uf.returnType,
    times: 0,
    defaultTimes: 0,
  }));

  const globalOutputVariables = [];
  const mallocOutputVariables = [];
  const stubOutputVariables = [];
  testCases.forEach((tc) => {
    const { output } = tc.data;
    // globals output
    if (output && output.global) {
      Object.keys(output.global).forEach((variableName) => {
        globalOutputVariables.push(variableName);
      });
    }
    // malloc output
    if (output && output.malloc) {
      Object.keys(output.malloc).forEach((variableName) => {
        const mallocExisted = malloc[variableName];
        if (mallocExisted) {
          const parsed = parseArrayTypeStr(mallocExisted['@type']);
          if (parsed) {
            mallocOutputVariables.push({
              typeName: mallocExisted['@type'],
              elementTypeName: parsed.elementType,
              length: parsed.length,
              variableName,
            });
          } else {
            mallocOutputVariables.push({
              typeName: mallocExisted['@type'],
              variableName,
            });
          }
        }
      });
    }

    // stub output
    if (output && output.stubs) {
      Object.keys(output.stubs).forEach((stubName) => {
        stubOutputVariables.push(stubName);
      });
    }
  });

  ps.push(functions.addMallocVariables(functionId, mallocs));
  ps.push(functions.addOutputMallocVariables(functionId, mallocOutputVariables));

  userStubs.forEach((stub) => ps.push(functions.addStub(functionId, stub)));

  [...new Set(globalOutputVariables)].forEach((variableName) => {
    const variable = utility.getValueByKeyPath(funcVariable, `variables.global.${variableName}`);
    if (variable) {
      ps.push(functions.addOutputGlobalVariable(functionId, variableName, variable));
    }
  });

  [...new Set(stubOutputVariables)].forEach((stubName) => {
    ps.push(functions.addStubParamsOutput(functionId, stubName));
  });

  await Promise.all(ps);
}

module.exports = {
  importDataTests,
};
